/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved.
 * secPaver is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package service

import (
	"context"
	"fmt"
	pb "gitee.com/openeuler/secpaver/api/proto/project"
	"gitee.com/openeuler/secpaver/common/ack"
	"gitee.com/openeuler/secpaver/common/log"
	"gitee.com/openeuler/secpaver/engine"
	repo "gitee.com/openeuler/secpaver/repository"
	"github.com/pkg/errors"
	"google.golang.org/grpc"
	"path/filepath"
	"sync"
	"time"
)

// ProjectService is the implement of ProjectMgr service
type ProjectService struct {
	sync.Mutex
	pb.UnimplementedProjectMgrServer

	repo    repo.Repo
	engines map[string]engine.Engine
	Name    string
}

// NewProjectService is the constructor of ProjectService
func NewProjectService(repo repo.Repo, engines map[string]engine.Engine) (*ProjectService, error) {
	return &ProjectService{
		Name:    "project",
		repo:    repo,
		engines: engines,
	}, nil
}

// ServiceName returns the name of service
func (s *ProjectService) ServiceName() string {
	return s.Name
}

// RegisterServer registers service in the grpc server
func (s *ProjectService) RegisterServer(server *grpc.Server) error {
	pb.RegisterProjectMgrServer(server, s)
	return nil
}

// BuildProject is the remote implement of the BuildProject rpc
func (s *ProjectService) BuildProject(req *pb.BuildReq, srv pb.ProjectMgr_BuildProjectServer) error {
	s.Lock()
	defer s.Unlock()

	if err := req.ValidateAll(); err != nil {
		return err
	}

	log.Infof("rpc request: BuildProject, project: %s, engine: %s",
		req.GetData().GetMeta().GetName(), req.GetEngine())

	builder, ok := s.engines[req.GetEngine()]
	if !ok || builder == nil {
		return fmt.Errorf("invalid engine %s", req.GetEngine())
	}

	ch := make(chan *pb.Ack, 1)
	defer close(ch)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		for {
			select {
			case value, ok := <-ch:
				if !ok {
					return
				}

				_ = srv.Send(value)
			case <-ctx.Done():
				return
			}
		}
	}()

	outDir := filepath.Join(s.repo.GetPolicyRoot(), req.GetEngine())

	if err := builder.Build(req.GetData(), outDir, ch); err != nil {
		log.Errorf("fail to build project: %v", err)
		return errors.Wrap(err, "fail to build project")
	}

	ch <- &pb.Ack{Level: ack.LevelInfo, Status: "Finish building project"}

	time.Sleep(time.Second)
	log.Infof("finish building project")

	return nil
}
